Add opt-in managed Python environment for FUSE mounting on macOS - #2528
Open
AlBannaTechno wants to merge 1 commit into
Open
Add opt-in managed Python environment for FUSE mounting on macOS#2528AlBannaTechno wants to merge 1 commit into
AlBannaTechno wants to merge 1 commit into
Conversation
Vorta's bundled Borg binary cannot ship FUSE support on macOS due to notarization and hardened-runtime restrictions, so the Mount feature fails for many users, and the documented Homebrew fuse-tap workaround breaks when FUSE-T (rather than macFUSE) provides the FUSE headers. This adds an alternative, fully opt-in mount mode that builds Borg from source with llfuse inside a private virtual environment, linking against whichever FUSE implementation is already installed. - New setting "Use managed Python environment for mounting" (macOS only, off by default) in a new Mounting group on the Settings view (opened via the "Settings / About" button; MiscTab in code), with a Prepare/Rebuild Environment button and live status output. - New vorta.borg.fuse_venv module: builds the venv at <app support>/Vorta/borg-fuse-venv/ via a JobsManager background job, pre-checks for pkg-config and surfaces known build failures (missing pkg-config, missing Xcode CLT) as actionable messages, and only marks the environment ready after validating borg --version and the llfuse import. - BorgMountJob.prepare_bin() now prefers the venv's borg binary when the setting is enabled and the environment is validated; all other Borg commands (create, prune, check, umount, ...) are untouched. - Unit tests covering setting declaration, binary selection, fallback behavior, and that non-mount jobs remain unaffected. Users who never enable the setting see zero behavior change.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
On macOS, the Mount feature is broken for a large class of users, for reasons outside their control:
.appbundle from dynamically loading external FUSE libraries (macFUSE/FUSE-T) at runtime, so shipping a mount-capable Borg inside the app is not feasible. This is a known, long-standing limitation.borgbackup/tap/borgbackup-fuseformula's build check requires/usr/local/include/fuse/fuse.hto exist and not be a symlink. On systems using FUSE-T — the modern, kext-less FUSE implementation — that path is a symlink to FUSE-T's headers, so the install is rejected outright even though FUSE-T is an API-compatible drop-in for macFUSE and its headers are perfectly valid.The net effect: a user with a healthy FUSE-T installation has no reasonable path to a working Mount button.
Solution
Building Borg from source via
pip install "borgbackup[llfuse]"inside an isolated virtual environment produces a mount-capable Borg on the first try — the compiler links against whatever FUSE headers are present at build time, without caring whether they belong to macFUSE or FUSE-T. This was validated manually on Apple Silicon with FUSE-T before implementation: the venv-built Borg mounts and browses real repositories with no symlink workarounds, no kext, no Recovery Mode.This PR wraps that recipe in a fully opt-in feature:
vorta.borg.fuse_venvmodule that builds the environment at<app support>/Vorta/borg-fuse-venv/as a background job on the existingJobsManagerinfrastructure (own site key, so it never queues behind repository jobs).BorgMountJob.prepare_bin(): when the setting is enabled and the environment has passed validation, the mount command uses the venv's Borg binary by absolute path. Nothing else changes.User flow
borg --versionruns andllfuseimports) before writing a readiness marker. Progress streams line-by-line into the UI.Robustness
pkg-configis detected before the build starts, and known failure signatures (missing pkg-config, missing Xcode Command Line Tools) are translated into messages with the exact command to run. The feature never installs anything system-wide itself.sys.executableis the Vorta binary when running from the bundled.app, so the venv is created with a discovered systempython3instead.venv --clearand clearly reports that a rebuild is in progress.Scope and compatibility
borg mountis affected. Create, prune, check, umount, scheduling, etc. keep the unchangedBorgJob.prepare_bin()resolution — a dedicated test asserts this.get_or_createsettings pass (no schema migration), and the entire feature is guarded to macOS.BorgJob.Changes
src/vorta/borg/fuse_venv.pysrc/vorta/borg/mount.pyBorgMountJob.prepare_bin()override (mount-only binary selection)src/vorta/store/settings.pysrc/vorta/views/misc_tab.pytests/unit/test_fuse_venv.pyTesting
Known limitation
The mount command syntax is derived from the system Borg's detected version (
borg_compat), while pip installs the latest stable 1.x. A user whose system Borg is a 2.x beta would get v2-style flags passed to the venv's 1.x binary. Considered acceptable for now given 2.x is pre-release; pinning or per-binary version detection can follow if needed.